﻿///////////////////////////////////////////////////////////////////////////////
// File:        Socket.hpp
// Author:      671643387@qq.com
// Description:
///////////////////////////////////////////////////////////////////////////////

#ifndef NIO_NET_SOCKET_HPP_
#define NIO_NET_SOCKET_HPP_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include <cassert>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <nio/Types.hpp>

#if defined(__LINUX__)
#include <fcntl.h>
#include <unistd.h>
#endif

namespace nio
{
    namespace net
    {
        class Socket : public boost::noncopyable
        {
        public:
            Socket(void);
            Socket(int family, int type, int protocol = 0);
            explicit Socket(sock_t sock);
            ~Socket(void);

            bool open(int family, int type, int protocol = 0)
            {
                sock_ = Socket::create(family, type, protocol);
                return (sock_ != INVALID_SOCKET);
            }

            void close(void)
            {
                if (sock_ != INVALID_SOCKET)
                {
                    Socket::close(sock_);
                    sock_ = INVALID_SOCKET;
                }
            }

            bool bind(const struct sockaddr* name, socklen_t namelen)
            {
                return Socket::bind(sock_, name, namelen);
            }

            bool connect(const struct sockaddr* name, socklen_t namelen)
            {
                return Socket::connect(sock_, name, namelen);
            }

            bool listen(int backlog)
            {
                return Socket::listen(sock_, backlog);
            }

            sock_t accept(struct sockaddr* name, socklen_t* namelen)
            {
                return Socket::accept(sock_, name, namelen);
            }

            int read(char* buff, unsigned int length, int flags = 0)
            {
                return Socket::read(sock_, buff, length, flags);
            }

            int write(const char* buff, unsigned int length, int flags = 0)
            {
                return Socket::write(sock_, buff, length, flags);
            }

            bool setOption(int level, int optname, const void* optval, socklen_t optlen)
            {
                return Socket::setOption(sock_, level, optname, optval, optlen);
            }

            bool getOption(int level, int optname, void* optval, socklen_t* optlen)
            {
                return Socket::getOption(sock_, level, optname, optval, optlen);
            }

            bool getError(int& err)
            {
                socklen_t errlen = sizeof(err);
                return Socket::getOption(sock_, SOL_SOCKET, SO_ERROR, (void*)&err, &errlen);
            }

            bool setNonblocking(bool on)
            {
                return Socket::setNonblocking(sock_, on);
            }

            bool setLinger(bool on)
            {
                return Socket::setLinger(sock_, on);
            }

            bool getpeername(struct sockaddr* addr, socklen_t* addrlen)
            {
                return Socket::getpeername(sock_, addr, addrlen);
            }

            bool getsockname(struct sockaddr* addr, socklen_t* addrlen)
            {
                return Socket::getsockname(sock_, addr, addrlen);
            }

            sock_t getSOCKET(void)
            {
                return sock_;
            }

            static sock_t create(int family, int type, int protocol = 0)
            {
                return ::socket(family, type, protocol);
            }

            static void close(sock_t sock)
            {
                assert(sock != INVALID_SOCKET);
#if defined(__WINDOWS__)
                ::closesocket(sock);
#elif defined(__LINUX__)
#endif
            }

            static bool bind(sock_t sock, const struct sockaddr* name, socklen_t namelen)
            {
                return (SOCKET_ERROR != ::bind(sock, name, namelen));
            }

            static bool connect(sock_t sock, const struct sockaddr* name, socklen_t namelen)
            {
                return (SOCKET_ERROR != ::connect(sock, name, namelen));
            }

            static bool listen(sock_t sock, int backlog)
            {
                return (SOCKET_ERROR != ::listen(sock, backlog));
            }

            static sock_t accept(sock_t sock, struct sockaddr* name, socklen_t* namelen)
            {
                return ::accept(sock, name, namelen);
            }

            static int read(sock_t sock, char* buff, unsigned int length, int flags);
            static int write(sock_t sock, const char* buff, unsigned int length, int flags);

            static bool setOption(sock_t sock, int level, int optname, const void* optval, socklen_t optlen)
            {
#if defined(__LINUX__)
                return (0 == ::setsockopt(sock, level, optname, optval, optlen));
#elif defined(__WINDOWS__)
                return (0 == ::setsockopt(sock, level, optname,
                    (const char*)optval, optlen));
#endif
            }

            static bool getOption(sock_t sock, int level, int optname, void* optval, socklen_t* optlen)
            {
#if defined(__LINUX__)
                return (0 == ::getsockopt(sock, level, optname, optval, optlen));
#elif defined(__WINDOWS__)
                return (0 == ::getsockopt(sock, level, optname, (char*)optval, optlen));
#endif
            }

            static bool setNonblocking(sock_t sock, bool on)
            {
#if defined(__WINDOWS__)
                ::u_long arg = (on) ? 1 : 0;
                return (0 == ::ioctlsocket(sock, FIONBIO, &arg));
#elif defined(__LINUX__)
                int flags = ::fcntl(sock, F_GETFL, 0);
                if (on) flags |= O_NONBLOCK;
                else flags &= ~O_NONBLOCK;
                return (-1 != ::fcntl(sock, F_SETFL, flags));
#endif
            }

            static bool setLinger(sock_t sock, bool on)
            {
                struct linger ling;
                ling.l_onoff = on ? 1 : 0;
                ling.l_linger = 0;
                return Socket::setOption(sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
            }

            static bool getpeername(sock_t sock, struct sockaddr* addr, socklen_t* addrlen)
            {
                return (0 == ::getpeername(sock, addr, addrlen));
            }

            static bool getsockname(sock_t sock, struct sockaddr* addr, socklen_t* addrlen)
            {
                return (0 == ::getsockname(sock, addr, addrlen));
            }

        private:
            sock_t sock_;
        };

        typedef boost::shared_ptr<Socket> SocketPtr;
    }
}

#endif //NIO_NET_SOCKET_HPP_
